package vip.liux.contracts.security.sms;

import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.util.Assert;
import vip.liux.contracts.security.AnonymousUserDetails;

import java.util.Map;
import java.util.Objects;

import static vip.liux.contracts.security.AuthenticationKit.*;

@Setter
@Getter
public class SmsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    private UserDetailsManager userDetailsManager;

    private CaptchaCache captchaCache = new MapCaptchaCache();

    /**
     * 身份验证检查
     *
     * @param userDetails    用户信息
     * @param authentication 认证信息
     * @throws AuthenticationException 认证异常
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (getCredentials(authentication).isEmpty()) { // 未提供验证码
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        String code = getCredentialsStr(authentication).orElse(""); // 获取验证码
        String mobile = getPrincipalStr(authentication).orElse("");
        // 测试万能码，建议仅开发环境开启
        if (StringUtils.equals(code, "999999")) {
            return;
        }
        Map<String, String> captcha = captchaCache.getCodeFromCache(mobile);// 获取缓存中的验证码
        if (captcha != null) {
            int times = Integer.parseInt(captcha.get("times"));
            if (times > 0 && Objects.equals(code, captcha.get("code"))) { // 验证码可验证次数大于0且验证码正确
                captchaCache.removeCodeFromCache(mobile); // 移除验证码
                return;
            } else {
                captchaCache.errorTimesIncrement(mobile); // 验证码错误次数加1
            }
        }
        // 验证码不存在或错误
        this.logger.debug("Failed to authenticate: verification code incorrect or expired");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }

    @Override
    protected void doAfterPropertiesSet() {
        Assert.notNull(this.captchaCache, "A CaptchaCache must be set");
        Assert.notNull(this.userDetailsManager, "A UserDetailsManager must be set");
    }

    /**
     * 根据手机号码获取用户信息
     *
     * @param username       手机号码
     * @param authentication 认证信息
     * @return 用户信息
     * @throws AuthenticationException 认证异常
     */
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        try {
            UserDetails loadedUser = this.getUserDetailsManager().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException ex) {
            return new AnonymousUserDetails(username, ""); // 返回一个无状态的用户信息, 若短信验证码正确则创建用户
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    /**
     * 创建成功的认证信息
     *
     * @param principal      主体 (用户名/手机号)
     * @param authentication 认证信息 (SmsCodeAuthenticationToken)
     * @param user           用户信息 (UserDetails)
     * @return 认证信息
     */
    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        // 如果用户是匿名用户，则创建用户
        if (user instanceof AnonymousUserDetails) {
            // 创建用户
            this.getUserDetailsManager().createUser(user);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
}